﻿using System;
using System.Collections.Specialized;
using System.ComponentModel;

namespace Loki.Utils
{
    public abstract class TrackedObject : LokiObject, INotifyPropertyChanged, ICentralizedChangeTracking, INotifyPropertyChanging
    {
        #region Constructor

        protected TrackedObject(IChangeTracking clone)
            : this()
        {
            stateChanged = clone.IsChanged;
        }

        protected TrackedObject()
        {
            IsTracking = true;
        }

        #endregion Constructor

        #region Change tracking

        public static readonly PropertyChangedEventArgs RefreshArgs = new PropertyChangedEventArgs(string.Empty);

        private static PropertyChangedEventArgs _IsDirtyChangeArgs = ObservableHelper.CreateChangedArgs<TrackedObject>(x => x.IsChanged);

        private static PropertyChangingEventArgs _IsDirtyChangingArgs = ObservableHelper.CreateChangingArgs<TrackedObject>(x => x.IsChanged);

        private bool stateChanged = false;

        /// <summary>
        /// Gets or sets a value indicating whether this instance is dirty.
        /// </summary>
        /// <value>Is <c>true</c> if this instance is dirty; otherwise, /c>.</value>
        public bool IsChanged
        {
            get
            {
                return stateChanged;
            }

            set
            {
                if (value != stateChanged)
                {
                    NotifyChanging(_IsDirtyChangingArgs);
                    stateChanged = value;
                    NotifyStateChanged(EventArgs.Empty);
                    NotifyChanged(_IsDirtyChangeArgs);
                }
            }
        }

        public bool IsTracking { get; set; }

        /// <summary>
        /// Starts the change IsTracking.
        /// </summary>
        public virtual void AcceptChanges()
        {
            IsChanged = false;
        }

        /// <summary>
        /// Notifies the change.
        /// </summary>
        /// <param name="e">Property changed event args.</param>
        public void NotifyChanged(PropertyChangedEventArgs e)
        {
            if (IsTracking)
            {
                OnPropertyChanged(e);
            }
        }

        public void Refresh()
        {
            NotifyChanged(RefreshArgs);
        }

        protected void NotifyChangedAndDirty(PropertyChangedEventArgs e)
        {
            if (IsTracking)
            {
                NotifyChanged(e);
                if (!IsChanged)
                {
                    IsChanged = true;
                }
            }
        }

        protected void NotifyChanging(PropertyChangingEventArgs e)
        {
            if (IsTracking)
            {
                OnPropertyChanging(e);
            }
        }

        protected void NotifyStateChanged(EventArgs e)
        {
            if (IsTracking)
            {
                OnStateChanged(e);
            }
        }

        protected void ObservedChanged()
        {
            if (IsTracking)
            {
                if (!IsChanged)
                {
                    IsChanged = true;
                }
            }
        }

        #endregion Change tracking

        #region State changed

        /// <summary>
        /// Occurs when internal state changed.
        /// </summary>
        public event EventHandler StateChanged;

        /// <summary>
        /// Raises the <see cref="StateChanged" /> event.
        /// </summary>
        /// <param name="e">The<see cref="EventArgs" /> object that provides the arguments for the
        /// event.</param>
        protected virtual void OnStateChanged(EventArgs e)
        {
            EventHandler L_Handler = StateChanged;

            if (L_Handler != null)
            {
                L_Handler(this, e);
            }
        }

        #endregion State changed

        #region Property changed

        /// <summary>
        /// Se produit lorsqu'une valeur de propriété est modifiée.
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;

        /// <summary>
        /// Raises the <see cref="PropertyChanged" /> event.
        /// </summary>
        /// <param name="e"><see cref="PropertyChangedEventArgs" /> object that provides the
        /// arguments for the event.</param>
        protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
        {
            PropertyChangedEventHandler L_Handler = PropertyChanged;

            if (L_Handler != null)
            {
                L_Handler(this, e);
            }
        }

        #endregion Property changed

        #region PropertyChanging

        /// <summary>
        /// Se produit lorsqu'une valeur de propriété va être est modifiée.
        /// </summary>
        public event PropertyChangingEventHandler PropertyChanging;

        /// <summary>
        /// Raises the <see cref="PropertyChanging" /> event.
        /// </summary>
        /// <param name="e"><see cref="PropertyChangedEventArgs" /> object that provides the
        /// arguments for the event.</param>
        protected virtual void OnPropertyChanging(PropertyChangingEventArgs e)
        {
            PropertyChangingEventHandler L_Handler = PropertyChanging;

            if (L_Handler != null)
            {
                L_Handler(this, e);
            }
        }

        #endregion PropertyChanging

        #region Initialize

        /// <summary>
        /// Signale à l'objet que l'initialisation démarre.
        /// </summary>
        public virtual void BeginInit()
        {
            IsTracking = false;
        }

        /// <summary>
        /// Signale à l'objet que l'initialisation est terminée and accepts changes.
        /// </summary>
        public void EndInit()
        {
            EndInit(true);
        }

        /// <summary>
        /// Signale à l'objet que l'initialisation est terminée.
        /// </summary>
        /// <param name="P_IsStable">If set to <c>true</c>, mark the entity as clean (accept
        /// changes) .</param>
        public virtual void EndInit(bool P_IsStable)
        {
            IsTracking = true;
            if (P_IsStable)
            {
                AcceptChanges();
            }
        }

        #endregion Initialize

        #region Sub collections

        protected virtual BindableCollection<T> CreateCollection<T>() where T : ICentralizedChangeTracking
        {
            return CreateCollection<T>(null);
        }

        protected virtual BindableCollection<T> CreateCollection<T>(Func<T> P_Adder) where T : ICentralizedChangeTracking
        {
            BindableCollection<T> L_Collection = new BindableCollection<T>(P_Adder);

            return L_Collection;
        }

        protected virtual BindableCollection<T> CreateTrackedCollection<T>() where T : ICentralizedChangeTracking
        {
            return CreateTrackedCollection<T>(null);
        }

        protected virtual BindableCollection<T> CreateTrackedCollection<T>(Func<T> P_Adder) where T : ICentralizedChangeTracking
        {
            BindableCollection<T> L_Collection = new BindableCollection<T>(P_Adder);

            L_Collection.ItemChanged += SubCollection_ItemChanged;
            L_Collection.CollectionChanged += SubCollection_CollectionChanged;

            return L_Collection;
        }

        protected T CreateTrackedObject<T>() where T : ICentralizedChangeTracking
        {
            return CreateTrackedObject<T>(null);
        }

        protected T CreateTrackedObject<T>(Func<T> P_Adder) where T : ICentralizedChangeTracking
        {
            Func<T> L_RealAdder = P_Adder == null ? ExpressionHelper.New<T>().Compile() : P_Adder;

            T L_Object = L_RealAdder();

            TrackChanges(L_Object);

            return L_Object;
        }

        protected virtual void SubCollection_CollectionChanged(object P_Sender, NotifyCollectionChangedEventArgs P_EventArgs)
        {
            ObservedChanged();
        }

        protected virtual void SubCollection_ItemChanged<T>(object P_Sender, ItemChangedEventArgs<T> P_EventArgs) where T : ICentralizedChangeTracking
        {
            if (P_EventArgs.Item.IsChanged)
            {
                ObservedChanged();
            }
        }

        protected void TrackChanges(ICentralizedChangeTracking P_Observed)
        {
            this.WatchStateChanged(P_Observed, o => o.Tracked_Changed);
        }

        private void Tracked_Changed(object P_Sender, EventArgs e)
        {
            ICentralizedChangeTracking L_Sender = P_Sender as ICentralizedChangeTracking;
            if (L_Sender != null && L_Sender.IsChanged)
            {
                ObservedChanged();
            }
        }

        #endregion Sub collections
    }
}